//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2007
//
// Pop-up menu with list of attributes of nearest objects.
//

# include "StdAfx.h"
# include "NearestRgnsList.h"
# include "Map.h"
# include "PAHelpers.h"
# include "Menu.h"
# include "Globals.h"

size_t _GetAreaCodeLen (const string_t & _str) {
	if (_str.empty ())
		return 0;
	const char * p = ::strchr (_str.c_str (), ')');
	return p == NULL ? 0 : p - _str.c_str () + 1;
}

static
bool _EqualAreaCodes (const string_t & _str1, const string_t & _str2) {
	const size_t cLen1 = _GetAreaCodeLen (_str1);
	const size_t cLen2 = _GetAreaCodeLen (_str2);
	if (cLen1 != cLen2 || cLen1 == 0)
		return false;

	return ::strncmp (_str1.c_str (), _str2.c_str (), cLen1) == 0;
}

///////////////////////////////////////////////////

struct nearest_rgn_t {
	float fD2;
	const rgn_t * pRgn;
	const void * pAttr;
	bool operator < (const nearest_rgn_t & _nr) const {return fD2 < _nr.fD2;}
};
typedef std::vector<nearest_rgn_t> nearest_rgns_t;
const size_t c_cMaxRgns = 10;

static
float _InsertNearestRgn (nearest_rgns_t & _nearest_rgns, const rgn_t * _pRgn, float _fD2, const void * _pAttr, bool _bAttrIsString, bool _bAttrIsPhone) {
	size_t cRgns = _nearest_rgns.size ();

	// Check if that attribute value is already presented.
	for (size_t c = 0; c < cRgns; ++ c) {
		nearest_rgn_t & nr = _nearest_rgns [c];

		bool bEqual = false;
		if (_bAttrIsString)
			bEqual = * reinterpret_cast<const string_t *> (_pAttr) == * reinterpret_cast<const string_t *> (nr.pAttr);
		else if (_bAttrIsPhone)
			bEqual = _EqualAreaCodes (* reinterpret_cast<const string_t *> (_pAttr), * reinterpret_cast<const string_t *> (nr.pAttr));
		else
			bEqual = _pAttr == nr.pAttr;

		if (bEqual) {
			if (nr.fD2 > _fD2) {
				// Replace the same value object with shorter distance.
				nr.fD2  = _fD2;
				nr.pRgn = _pRgn;

				std::sort (_nearest_rgns.begin (), _nearest_rgns.end ());
			}

			return cRgns < c_cMaxRgns ? -1 : _nearest_rgns [cRgns - 1].fD2;
		}
	}

	// Insert new attribute value.
	_nearest_rgns.push_back (nearest_rgn_t ());
	nearest_rgn_t & nr = _nearest_rgns.back ();
	nr.fD2   = _fD2;
	nr.pRgn  = _pRgn;
	nr.pAttr = _pAttr;
	++ cRgns;

	std::sort (_nearest_rgns.begin (), _nearest_rgns.end ());

	// Fix overflow condition.
	if (cRgns > c_cMaxRgns) {
		_nearest_rgns.pop_back ();
		-- cRgns;
	}

	// Return the biggest distance among nearest visited objects.
	return cRgns < c_cMaxRgns ? -1 : _nearest_rgns [cRgns - 1].fD2;
}

static
const void * _GetAttr (const rgn_t & _rgn, nr_attr_t _attr) {
	if (_attr == nrNumber || _attr == nrStreet || _attr == nrPhone) {
		if (_attr == nrPhone) {
			const string_t & str = ! _rgn.pExtra->strPhone.empty () ? _rgn.pExtra->strPhone : _rgn.pExtra->strFax;
			if (str.empty () || _GetAreaCodeLen (str) == 0)
				return NULL;
			return & str;
		}
		const string_t & str = _attr == nrNumber ? _rgn.pExtra->strNumber : _rgn.pExtra->strStreetDesc;
		if (str.empty ())
			return NULL;
		return & str;
	} else {
		assert (_attr == nrCity || _attr == nrZip);
		if (_attr == nrCity) {
			if (_rgn.pExtra->pCity == NULL || _rgn.pExtra->pCity->IsDeleted ())
				return NULL;
			return (const void *) _rgn.pExtra->pCity;
		} else {
			if (_rgn.pExtra->pZip == NULL || _rgn.pExtra->pZip->IsDeleted ())
				return NULL;
			return (const void *) _rgn.pExtra->pZip;
		}
	}
}

bool ThereAreNearestRgns (nr_attr_t _attr) {
	CCritSec::CLock lock (g_cs);

	for (size_t cKind = 0; cKind < rkMax; ++ cKind) {
		const layer_t & layer = g_map.layers [cKind];
		const rgns_t & rgns = layer.rgns;
		for (rgns_t::const_iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
			const rgn_t & rgn = * iRgn;
			if (rgn.IsDeleted () || rgn.IsWTR ())
				continue;

			if (! rgn.pExtra)
				continue;
			const void * const pAttr = _GetAttr (rgn, _attr);
			if (pAttr != NULL)
				return true;
		}
	}

	return false;
}

static DWORD g_dwCancelTime_ShowNearestRgns;

pa_item_t * ShowNearestRgnsList (HWND _hWnd, HWND _hwndButton, UINT _uiEditBox, nr_attr_t _attr, const point_t & _p, rgn_t * _pRgnExcept) {
	if (::GetTickCount () - g_dwCancelTime_ShowNearestRgns < 200)
		// NOTE: this click to arrow button is to close previous pop-up menu.
		return NULL;

	const bool bAttrIsString = _attr == nrNumber || _attr == nrStreet;
	const bool bAttrIsPhone  = _attr == nrPhone;

	CCritSec::CLock lock (g_cs);

	//
	// Prepare list of nearest objects.
	//
	nearest_rgns_t nearest_rgns;

	float fD2Min = -1;
	for (size_t cKind = 0; cKind < rkMax; ++ cKind) {
		const layer_t & layer = g_map.layers [cKind];
		const rgns_t & rgns = layer.rgns;
		for (rgns_t::const_iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
			const rgn_t & rgn = * iRgn;
			if (rgn.IsDeleted () || rgn.IsWTR ())
				continue;
			if (& rgn == _pRgnExcept)
				continue;

			if (! rgn.pExtra)
				continue;
			const void * const pAttr = _GetAttr (rgn, _attr);
			if (pAttr == NULL)
				continue;

			if (rgn.IsPoint ()) {
				const size_t cOrigins = rgn.origins.size ();
				for (size_t c = 0; c < cOrigins; ++ c) {
					const float fD2 = Distance2 (rgn.origins [c].point, _p, g_map.fCosY);
					if (fD2Min < 0 || fD2Min > fD2)
						fD2Min = _InsertNearestRgn (nearest_rgns, & rgn, fD2, pAttr, bAttrIsString, bAttrIsPhone);
				}
			} else {
				const bool bPolyline = rgn.IsPolyline ();
				const size_t cElements = rgn.elements.size ();
				for (size_t c = 0; c < cElements; ++ c) {
					const rgn_element_t & element = rgn.elements [c];
					if (element.bDeleted)
						continue;

					const points_t & points = element.points;

					const float fD2 = bPolyline ?
						Distance2ToPolyline (points, _p, g_map.fCosY) :
						Distance2ToPolygon  (points, _p, g_map.fCosY);
					if (fD2Min < 0 || fD2Min > fD2)
						fD2Min = _InsertNearestRgn (nearest_rgns, & rgn, fD2, pAttr, bAttrIsString, bAttrIsPhone);
				}
			}
		}
	}
	if (nearest_rgns.empty () && ! bAttrIsPhone)
		return NULL;

	//
	// Prepare pop-up menu.
	//
	CPopupMenu pm;
	if (bAttrIsPhone)
		pm.AppendItem (1, ", ");
	for (size_t c = 0; c < nearest_rgns.size (); ++ c) {
		const rgn_t * const pRgn = nearest_rgns [c].pRgn;
		char str [128];
		switch (_attr) {
			default:
			case nrNumber: ::strcpy (str, pRgn->pExtra->strNumber     .c_str ()); break;
			case nrStreet: ::strcpy (str, pRgn->pExtra->strStreetDesc .c_str ()); break;
			case nrCity:   FillCity (str, ARRAY_SIZE (str), pRgn->pExtra->pCity, ""); break;
			case nrZip:    ::strcpy (str, pRgn->pExtra->pZip ->strName.c_str ()); break;
			case nrPhone: {
				const string_t & strPhone = ! pRgn->pExtra->strPhone.empty () ? pRgn->pExtra->strPhone : pRgn->pExtra->strFax;
				const size_t cAreaCodeLen = __min (_GetAreaCodeLen (strPhone), ARRAY_SIZE (str) - 2);
				::strncpy (str, strPhone.c_str (), cAreaCodeLen);
				str [cAreaCodeLen + 0] = ' ';
				str [cAreaCodeLen + 1] = '\0';
				break;
			}
		}
		pm.AppendItem (1 + c + (bAttrIsPhone ? 1 : 0), str);
	}
	const size_t cItems = nearest_rgns.size () + (bAttrIsPhone ? 1 : 0);

	RECT rect;
	::GetWindowRect (_hwndButton, & rect);

	//
	// Show popup menu.
	//
	const int nCmd = pm.ShowAsPopup (_hWnd, rect);

	pa_item_t * pRet = NULL;
	if (nCmd > 0 && nCmd - 1 < (int) cItems) {
		// Set the selected text to the desired edit box.
		char strText [256];
		::GetMenuString (pm, nCmd, strText, ARRAY_SIZE (strText), MF_BYCOMMAND);
		if (bAttrIsPhone)
			::SendDlgItemMessage (_hWnd, _uiEditBox, EM_REPLACESEL, TRUE, reinterpret_cast<LPARAM> (strText));
		else
			::SetDlgItemText (_hWnd, _uiEditBox, strText);

		if (_attr == nrCity)
			pRet = nearest_rgns [nCmd - 1].pRgn->pExtra->pCity;
		else if (_attr == nrZip)
			pRet = nearest_rgns [nCmd - 1].pRgn->pExtra->pZip;
	} else
		g_dwCancelTime_ShowNearestRgns = ::GetTickCount ();

	return pRet;
}
